package de.bloxel.core.events;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * core of the event system {@link EventListener} can subscribe specific event types (or all (eventType="ALL") and will called when an event of the
 * subscribed type is triggered.
 * 
 * @since 0.1.0
 * @author dorer
 * @author Klaus Hauschild
 */
public class EventManager {

  private final Map<String, Set<EventListener>> eventListeners = new HashMap<String, Set<EventListener>>();
  @SuppressWarnings("unchecked")
  private final List<Event>[] eventQueue = new List[2];
  private int index;

  public EventManager() {
    eventQueue[0]=new ArrayList<Event>();
    eventQueue[1]=new ArrayList<Event>();
  }

  /**
   * subscribes an eventListener to a specific eventType use "ALL" as eventType to receive all events.
   * 
   * @param type
   *          the event type which should be subscribed
   * @param listener
   *          the {@link EventListener} who should be notified
   */
  public void subscribeEvent(final String type, final EventListener listener) {
    if (eventListeners.containsKey(type)) {
      eventListeners.get(type).add(listener);
    } else {
      final Set<EventListener> listenerSet=new HashSet<EventListener>();
      listenerSet.add(listener);
      eventListeners.put(type, listenerSet);
    }
  }

  /**
   * unsubscribe a listener for a specific event type.
   * 
   * @param type
   *          the event type
   * @param listener
   *          the listener
   */
  public void unsubscribeEvent(final String type, final EventListener listener) {
    if (eventListeners.containsKey(type) && eventListeners.get(type).contains(listener)) {
      eventListeners.get(type).remove(listener);
    }
  }

  /**
   * removes all event subscriptions for a {@link EventListener}.
   * 
   * @param listener
   *          the listener
   */
  public void unsubscribeAll(final EventListener listener) {
    for(final Set<EventListener> s:eventListeners.values()) {
      if (s.contains(listener)) {
        s.remove(listener);
      }
    }
  }

  /**
   * queues an event for processing.
   * 
   * @param event
   *          the event
   */
  public void queueEvent(final Event event) {
    eventQueue[index].add(event);
  }

  /**
   * processes all event in the current queue two event queues are used to avoid an endless loop if an {@link EventListener} triggers another event.
   */
  public void doEvents() {
    final int current=index;

    //switch to other event queue for new events
    index=(index+1)%2;

    while(!eventQueue[current].isEmpty()) {
      final Event currentEvent=eventQueue[current].remove(0);

      //send events to listeners listening for all events
      if (eventListeners.containsKey("ALL")) {
        for(final EventListener listener:eventListeners.get("ALL")) {
          listener.handleEvent(currentEvent);
        }
      }

      //send events to subscribed listeners
      if (eventListeners.containsKey(currentEvent.getType())) {
        for(final EventListener listener: eventListeners.get(currentEvent.getType())) {
          if (listener.handleEvent(currentEvent)) {
            break;
          }
        }
      }
    }
  }

}
